package io.renren.wechat.service.suite;

import io.renren.wechat.config.WechatProperties;
import io.renren.wechat.service.suite.dto.*;
import io.renren.wechat.service.token.AccessTokenService;
import io.renren.wechat.service.token.dto.AccessTokenResponse;
import io.renren.wechat.support.ResponseEntityUtils;
import io.renren.wechat.support.WechatConstants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 与应用相关的服务类
 *
 * @author dmcp@qq.com
 * @since 2021-11-17 22:20
 */

@Component
public class SuiteService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SuiteService.class);
    private RestTemplate fastRestTemplate;
    private SuiteAccessTokenService suiteAccessTokenService;
    private AccessTokenService accessTokenService;
    private SuiteTicketService suiteTicketService;
    private StringRedisTemplate stringRedisTemplate;
    private WechatProperties wechatProperties;
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private RedisTemplate<String, Object> redisTemplate;

    private static final int AGENT_ID_CACHE_TTL = 120;

    /**
     * 获取预授权码
     * 该API用于获取预授权码。预授权码用于企业授权时的第三方服务商安全验证。
     *
     * @param suiteAccessToken 第三方应用access_token,最长为512字节
     * @return 预授权码, 最长为512字节
     */
    public String getPreAuthCode(String suiteAccessToken) {
        final SuitePreAuthCodeResponse suitePreAuthCodeResponse = this.doGetPreAuthCode(suiteAccessToken);

        return suitePreAuthCodeResponse.getPreAuthCode();
    }

    public String getPermanentCodeFromCache(String suiteId, String targetCorpId) {
        Objects.requireNonNull(suiteId, "suiteId is required for getPermanentCode");
        Objects.requireNonNull(targetCorpId, "targetCorpId is required for getPermanentCode");

        final String key = buildPermanentCodeKey(suiteId, targetCorpId);

        final String permanentCode = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(permanentCode)) {
            return permanentCode;
        }

        LOGGER.warn("permanentCode not in cache now, suiteId: {}, targetCorpId: {}", suiteId, targetCorpId);
        throw new RuntimeException("permanentCode not in cache now");
    }

    public String getPermanentCode(String suiteId, String targetCorpId) {
        Objects.requireNonNull(suiteId, "suiteId is required for getPermanentCode");
        Objects.requireNonNull(targetCorpId, "targetCorpId is required for getPermanentCode");

        final String key = buildPermanentCodeKey(suiteId, targetCorpId);

        final String permanentCode = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(permanentCode)) {
            return permanentCode;
        }

        final PermanentAuthInfoResponse permanentAuthInfoResponse = doGetPermanentCode(suiteId, null);

        return permanentAuthInfoResponse.getPermanentCode();
    }

    /**
     * 获取企业永久授权码
     * 该API用于使用临时授权码换取授权方的永久授权码，并换取授权信息、企业access_token，临时授权码一次有效。
     * 建议第三方以userid为主键，来建立自己的管理员账号。
     *
     * @param suiteId 临时授权码，会在授权成功时附加在redirect_uri中跳转回第三方服务商网站，
     *                或通过授权成功通知回调推送给服务商。长度为64至512个字节
     * @return 企业永久授权码
     */
    public PermanentAuthInfoResponse doGetPermanentCode(String suiteId, String authCode) {
        final String suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken();
        Map<String, String> params = new HashMap<>();

        /* 临时授权码，会在授权成功时附加在redirect_uri中跳转回第三方服务商网站，或通过回调推送给服务商。长度为64至512个字节 */
        String authCodeString = authCode;
        if (StringUtils.isBlank(authCodeString)) {
            authCodeString = this.getAuthCode(suiteId);
        }
        params.put("auth_code", authCodeString);

        String url = String.format(WechatConstants.SUITE_GET_PERMANENT_CODE_URL, suiteAccessToken);

        final PermanentAuthInfoResponse permanentAuthInfoResponse = doGetCorpAuthInfo(url, params);

        cachePermanentCode(suiteId, permanentAuthInfoResponse);

        return permanentAuthInfoResponse;
    }

    private void cachePermanentCode(String suiteId, PermanentAuthInfoResponse permanentAuthInfoResponse) {
        final String corpId = permanentAuthInfoResponse.getAuthCorpInfo().getCorpId();

        /* cache it */
        String key = buildPermanentCodeKey(suiteId, corpId);
        stringRedisTemplate.opsForValue().set(key, permanentAuthInfoResponse.getPermanentCode());

        accessTokenService.cacheAccessToken(permanentAuthInfoResponse.getAccessToken(),
                permanentAuthInfoResponse.getExpiresIn(),
                permanentAuthInfoResponse.getAuthCorpInfo().getCorpId());
    }

    public String getAuthCode(String suiteId) {
        return suiteTicketService.getAuthCode(suiteId);
    }

    public PermanentAuthInfoResponse onCreateAuth(String suiteId, String thirdCorpId, String authCode) {
        final PermanentAuthInfoResponse permanentAuthInfoResponse = this.doGetPermanentCode(suiteId, authCode);

        if (null != permanentAuthInfoResponse) {
            cacheCorpInfo(permanentAuthInfoResponse);
        }

        return permanentAuthInfoResponse;
    }

    /* delete cache */
    public void onCancelAuth(String suiteId, String authCorpId) {
        final String permanentCodeKey = buildPermanentCodeKey(suiteId, authCorpId);
        stringRedisTemplate.delete(permanentCodeKey);

        final String accessTokenCacheKey = buildAuthCorpAccessTokenCacheKey(authCorpId);
        stringRedisTemplate.delete(accessTokenCacheKey);

        final String corpCacheKey = buildCorpCacheKey(authCorpId);
        redisTemplate.delete(corpCacheKey);
    }

    private void cacheCorpInfo(PermanentAuthInfoResponse permanentCode) {
        final AuthCorpInfo authCorpInfo = permanentCode.getAuthCorpInfo();
        String cacheKey = buildCorpCacheKey(authCorpInfo.getCorpId());

        redisTemplate.opsForValue().set(cacheKey, permanentCode, 1, TimeUnit.DAYS);
    }

    private String buildCorpCacheKey(String corpId) {
        return String.format("CORP_INFO:%s", corpId);
    }

    public PermanentAuthInfoResponse getCorpInfo(String corpId) {
        final String corpCacheKey = this.buildCorpCacheKey(corpId);

        final Object corpInfo = redisTemplate.opsForValue().get(corpCacheKey);
        if (null != corpInfo) {
            LOGGER.debug("got corp cache info from cache with corpId: {}", corpId);
            return (PermanentAuthInfoResponse) corpInfo;
        }

        final String suiteId = wechatProperties.getSuiteId();
        final String permanentCode = this.getPermanentCodeFromCache(suiteId, corpId);

        final PermanentAuthInfoResponse corpAuthInfo = this.getCorpAuthInfo(corpId, permanentCode);
        if (null != corpAuthInfo) {
            cacheCorpInfo(corpAuthInfo);
        }
        return corpAuthInfo;
    }

    /**
     * 获取企业授权信息
     * 该API用于通过永久授权码换取企业微信的授权信息。
     * 永久code的获取，是通过临时授权码使用get_permanent_code 接口获取到的permanent_code。
     *
     * @return 企业微信的授权信息
     */
    public PermanentAuthInfoResponse getCorpAuthInfo(String authCorpId, String permanentCode) {
        final String suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken();
        Map<String, String> params = new HashMap<>();
        params.put("auth_corpid", authCorpId);
        params.put("permanent_code", permanentCode);

        String url = String.format(WechatConstants.SUITE_GET_CORP_AUTH_INFO_URL, suiteAccessToken);

        return doGetCorpAuthInfo(url, params);
    }

    public String getAuthCorpAccessToken(String authCorpId, Integer state) {
        final String accessTokenFromCache = accessTokenService.getAuthCorpAccessTokenFromCache(authCorpId);
        if (StringUtils.isNotBlank(accessTokenFromCache)) {
            return accessTokenFromCache;
        }

        String suiteId = wechatProperties.getSuiteId();
        if (123457 == state) {
            suiteId = wechatProperties.getSuiteId2();
        }
        final String permanentCode = this.getPermanentCodeFromCache(suiteId, authCorpId);

        final AccessTokenResponse corpAccessToken = this.getAuthCorpAccessTokenInner(authCorpId, permanentCode);

        return corpAccessToken.getAccessToken();
    }

    /**
     * 获取企业access_token
     * 第三方服务商在取得企业的永久授权码后，通过此接口可以获取到企业的access_token。
     * 获取后可通过通讯录、应用、消息等企业接口来运营这些应用。
     */
    private AccessTokenResponse getAuthCorpAccessTokenInner(String authCorpId, String permanentCode) {
        final String suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken();
        Map<String, String> params = new HashMap<>();
        params.put("auth_corpid", authCorpId);
        params.put("permanent_code", permanentCode);

        String url = String.format(WechatConstants.SUITE_GET_CORP_ACCESS_TOKEN_URL, suiteAccessToken);

        final AccessTokenResponse accessTokenResponse = accessTokenService.doGetAccessToken(url, params);
        accessTokenService.cacheAuthCorpAccessToken(accessTokenResponse, authCorpId);

        return accessTokenResponse;
    }

    /**
     * 第三方服务商可以用此接口获取授权企业中某个第三方应用的管理员列表(不包括外部管理员)，
     * 以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
     *
     * @param authCorpId 授权方corpid
     * @param agentId    授权方安装的应用agentid
     * @return 管理员列表
     */
    public List<AdminInfo> getAdminList(String authCorpId, String agentId, String state) {
        String suiteAccessToken;
        if ("123457".equals(state)) {
            suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken2();
        } else {
            suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken();
        }

        Map<String, String> params = new HashMap<>();
        params.put("auth_corpid", authCorpId);
        params.put("agentid", agentId);

        LOGGER.debug("getAdminList params, suiteAccessToken: {}, params: {}", suiteAccessToken, params);

        String url = String.format(WechatConstants.SUITE_GET_ADMIN_LIST_URL, suiteAccessToken);

        final ResponseEntity<AdminListResponse> responseEntity = fastRestTemplate.postForEntity(url, params, AdminListResponse.class);

        LOGGER.debug("getAdminList response entity: {}", responseEntity);

        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("调用获取应用的管理员列表[get_admin_list]请求执行失败");
        }

        final AdminListResponse response = responseEntity.getBody();
        if (response == null) {
            LOGGER.warn("obtain get_admin_list failed, response body is empty!");
            throw new RuntimeException("获取 get_admin_list 失败，响应为空");
        }

        boolean isError = ResponseEntityUtils.isError(responseEntity) || response.isError();
        if (isError) {
            LOGGER.warn(">>>>>>>obtain get_admin_list failed, errMsg: {}", response.getErrmsg());
            throw new RuntimeException("获取 get_admin_list 失败");
        }

        return response.getAdminInfo();
    }

    public void cacheVisitingAgentId(String corpId, String agentId, String visitorUserId, String suiteToken) {
        LOGGER.debug("cache VisitingAgentId with corpId: {}, agentId: {}, visitorUserId: {}, suiteToken: {}", corpId, agentId, visitorUserId, suiteToken);

        String key = buildVisitingAgentKey(corpId, visitorUserId, suiteToken);
        stringRedisTemplate.opsForValue().set(key, agentId);
//        stringRedisTemplate.opsForValue().set(key, agentId, AGENT_ID_CACHE_TTL, TimeUnit.SECONDS);


    }

    public String getVisitingAgentIdFromCache(String corpId, String visitorUserId, String suiteToken) {

        String key = buildVisitingAgentKey(corpId, visitorUserId, suiteToken);

        return stringRedisTemplate.opsForValue().get(key);
    }

    private String buildVisitingAgentKey(String corpId, String visitorUserId, String suiteToken) {
        return String.format("cv:%s:%s:%s", corpId, visitorUserId, suiteToken);
    }

    public UserClientInfoResponse getClientInfoInner(String code, String suiteAccessToken) {
        LOGGER.debug("getClientInfo using params suiteAccessToken: {}, code: {}", suiteAccessToken, code);

        String requestUrl = String.format(WechatConstants.SUITE_GET_USER_INFO_3RD_URL, suiteAccessToken, code);

        final ResponseEntity<UserClientInfoResponse> responseEntity = fastRestTemplate.getForEntity(requestUrl, UserClientInfoResponse.class);

        LOGGER.debug("get userInfo3rd response entity: {}", responseEntity);

        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("调用获取应用的管理员列表[get_admin_list]请求执行失败");
        }

        final UserClientInfoResponse response = responseEntity.getBody();
        if (response == null) {
            LOGGER.warn("obtain userInfo3rd failed, response body is empty!");
            throw new RuntimeException("获取 userInfo3rd 失败，响应为空");
        }

        boolean isError = ResponseEntityUtils.isError(responseEntity) || response.isError();
        if (isError) {
            LOGGER.warn(">>>>>>>obtain userInfo3rd failed, errMsg: {}", response.getErrmsg());
            throw new RuntimeException("获取 userInfo3rd 失败");
        }


        threadPoolTaskExecutor.submit(() -> {
            cacheUserTicket(response);

            try {
                final UserDetailInfo userDetailInfo = getUserDetailInfo(response.getCorpId(), response.getUserId());
                if (null != userDetailInfo) {
                    cacheUserDetailInfo(userDetailInfo);
                }
            } catch (Exception e) {
                LOGGER.error("get userDetailInfo failed", e);
            }
        });

        return response;
    }

    /**
     * 第三方根据code获取企业成员信息
     */
    public UserClientInfoResponse getClientInfo(String code) {
        LOGGER.info("getClientInfo, code: {}", code);
        final String suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken();

        return getClientInfoInner(code, suiteAccessToken);
    }

    /**
     * 第二个应用，第三方根据code获取企业成员信息
     */
    public UserClientInfoResponse getClientInfo2(String code) {
        LOGGER.info("getClientInfo2, code: {}", code);

        final String suiteAccessToken = suiteAccessTokenService.getSuiteAccessToken2();

        return getClientInfoInner(code, suiteAccessToken);
    }

    private void cacheUserDetailInfo(UserDetailInfo userDetailInfo) {
        final String key = buildUserDetailInfoCacheKey(userDetailInfo.getCorpId(), userDetailInfo.getUserId());
        redisTemplate.opsForValue().set(key, userDetailInfo, 1, TimeUnit.DAYS);
    }

    public UserDetailInfo getUserDetailInfoFromCache(String corpId, String userId) {
        final String key = buildUserDetailInfoCacheKey(corpId, userId);
        final Object userDetailInfo = redisTemplate.opsForValue().get(key);

        if (null != userDetailInfo) {
            LOGGER.debug("got userDetailInfo from cache with key: {}, userDetailInfo: {}", key, userDetailInfo);

            return (UserDetailInfo) userDetailInfo;
        } else {
            LOGGER.debug("can not get userDetailInfo from cache with key: {}", key);
        }

        return null;
    }

    private String buildUserDetailInfoCacheKey(String corpId, String userId) {
        return String.format("userDetailInfo:%s:%s", corpId, userId);
    }

    public UserDetailInfo getUserDetailInfo(String corpId, String userId) {
        final UserDetailInfo userDetailInfoFromCache = getUserDetailInfoFromCache(corpId, userId);

        if (null != userDetailInfoFromCache) {
            LOGGER.debug("got userDetailInfo from cache: {}", userDetailInfoFromCache);

            return userDetailInfoFromCache;
        }

        return this.getUserDetailInfo(corpId, userId, null);
    }

    public UserDetailInfo getUserDetailInfo(String corpId, String userId, String suiteAccessToken) {
        String token = suiteAccessToken;
        if (StringUtils.isBlank(token)) {
            token = suiteAccessTokenService.getSuiteAccessToken();
        }

        final String userTicket = getUserTicketFromCache(corpId, userId);
        if (StringUtils.isBlank(userTicket)) {
            LOGGER.debug("can not get userTicket from cache with corpId: {} userId: {}", corpId, userId);
            return this.getUserDetailInfoFromCache(corpId, userId);
        }

        LOGGER.debug("getUserDetailInfo params suiteAccessToken: {}, userTicket: {}", token, userTicket);
        String requestUrl = String.format(WechatConstants.SUITE_GET_USER_DETAIL_3RD_URL, token);
        Map<String, Object> params = new HashMap<>();
        params.put("user_ticket", userTicket);

        final ResponseEntity<UserDetailInfo> responseEntity = fastRestTemplate.postForEntity(requestUrl, params, UserDetailInfo.class);

        LOGGER.debug("get userDetailInfo response entity: {}", responseEntity);

        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("调用获取应用的管理员列表[get_admin_list]请求执行失败");
        }

        final UserDetailInfo response = responseEntity.getBody();
        if (response == null) {
            LOGGER.warn("obtain userDetailInfo failed, response body is empty!");
            throw new RuntimeException("获取 userDetailInfo 失败，响应为空");
        }

        boolean isError = ResponseEntityUtils.isError(responseEntity) || response.isError();
        if (isError) {
            LOGGER.warn(">>>>>>>obtain userDetailInfo failed, errMsg: {}", response.getErrmsg());
            throw new RuntimeException("获取 userDetailInfo 失败");
        }

        LOGGER.debug("got userDetailInfo: {} with corpId: {}, userId: {}", response, corpId, userId);

        return response;
    }

    public void cacheUserTicket(UserClientInfoResponse response) {
        final String userId = response.getUserId();
        final String userTicket = response.getUserTicket();

        final String ticketCacheKey = buildUserTicketCacheKey(response.getCorpId(), userId);
        LOGGER.debug("caching userTicket: {} with cacheKey: {}", userTicket, ticketCacheKey);

        stringRedisTemplate.opsForValue().set(ticketCacheKey, userTicket, response.getExpiresIn(), TimeUnit.SECONDS);
    }

    public String getUserTicketFromCache(String corpId, String userId) {
        final String userTicket = stringRedisTemplate.opsForValue().get(buildUserTicketCacheKey(corpId, userId));

        if (StringUtils.isNotBlank(userTicket)) {
            LOGGER.info("got userTicket from cache with corpId: {}, userId: {}", corpId, userId);
        }

        return userTicket;
    }

    private String buildUserTicketCacheKey(String corpId, String userId) {
        return String.format("userTicket:%s:%s", corpId, userId);
    }

    private PermanentAuthInfoResponse doGetCorpAuthInfo(String url, Map<String, String> params) {
        final ResponseEntity<PermanentAuthInfoResponse> responseEntity = fastRestTemplate.postForEntity(url, params, PermanentAuthInfoResponse.class);

        LOGGER.debug("get corp permanent code response entity: {}", responseEntity);

        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("调用获取获取企业永久授权码[get_permanent_code]请求执行失败");
        }

        final PermanentAuthInfoResponse tokenResponse = responseEntity.getBody();
        if (tokenResponse == null) {
            LOGGER.warn("obtain get_permanent_code failed, response body is empty!");
            throw new RuntimeException("获取get_permanent_code失败，响应为空");
        }

        boolean isError = ResponseEntityUtils.isError(responseEntity) || tokenResponse.isError();
        if ((isError)) {
            LOGGER.warn(">>>>>>>obtain get_permanent_code failed, errMsg: {}", tokenResponse.getErrmsg());
            throw new RuntimeException("获取 get_permanent_code 失败");
        }

        LOGGER.info("got CorpAuthInfo: {}", tokenResponse);

        return tokenResponse;
    }

    public SuitePreAuthCodeResponse doGetPreAuthCode(String suiteAccessToken) {
        final String url = String.format(WechatConstants.SUITE_GET_PRE_AUTH_CODE_URL, suiteAccessToken);

        final ResponseEntity<SuitePreAuthCodeResponse> responseEntity = fastRestTemplate.getForEntity(url, SuitePreAuthCodeResponse.class);
        LOGGER.debug("get suite pre auth code response entity: {}", responseEntity);

        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("调用获取 suite pre auth code 请求执行失败");
        }

        final SuitePreAuthCodeResponse tokenResponse = responseEntity.getBody();
        if (tokenResponse == null) {
            LOGGER.warn("obtain suite access token failed, response body is empty!");
            throw new RuntimeException("获取suite accessToken失败，响应为空");
        }

        boolean isError = ResponseEntityUtils.isError(responseEntity) || tokenResponse.isError();
        if ((isError)) {
            LOGGER.warn(">>>>>>>obtain suite access token failed, errMsg: {}", tokenResponse.getErrMsg());
            throw new RuntimeException("获取suite accessToken失败");
        }

        return tokenResponse;
    }

    private String buildPermanentCodeKey(String suiteId, String targetCorpId) {
        return String.format("permanent_code:%s:%s", suiteId, targetCorpId);
    }

    /* 授权企业的token */
    private String buildAuthCorpAccessTokenCacheKey(String corpid) {
        return String.format("auth_access_token:%s", corpid);
    }

    @Autowired
    public void setAccessTokenService(AccessTokenService accessTokenService) {
        this.accessTokenService = accessTokenService;
    }

    @Autowired
    public void setSuiteAccessTokenService(SuiteAccessTokenService suiteAccessTokenService) {
        this.suiteAccessTokenService = suiteAccessTokenService;
    }

    @Autowired
    public void setFastRestTemplate(RestTemplate fastRestTemplate) {
        this.fastRestTemplate = fastRestTemplate;
    }

    @Autowired
    public void setSuiteTicketService(SuiteTicketService suiteTicketService) {
        this.suiteTicketService = suiteTicketService;
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Autowired
    public void setWechatProperties(WechatProperties wechatProperties) {
        this.wechatProperties = wechatProperties;
    }

    @Autowired
    public void setThreadPoolTaskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
